﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Xml;

namespace Moonlite.Data.Configuration
{
    /// <summary>
    /// Represents a configuration instance that holds child configuration elements.
    /// </summary>
    public abstract class ConfigurationInstance : IDumpable
    {
        internal event PropertyChangedEventHandler PropertyChanged;

        bool inited;
        ConfigurationElementCollection elements = new ConfigurationElementCollection();

        /// <summary>
        /// Gets the configuration elements.
        /// </summary>
        /// <value>The configuration elements.</value>
        public ConfigurationElementCollection Elements
        {
            get { return elements; }
        }

        /// <summary>
        /// Gets the name.
        /// </summary>
        /// <value>The name.</value>
        public abstract string Name { get; }

        /// <summary>
        /// Gets the allowed element types.
        /// </summary>
        /// <value>The allowed element types.</value>
        public abstract Type[] AllowedElementTypes { get; }

        /// <summary>
        /// Gets a value indicating whether this <see cref="ConfigurationElement"/> is initialized.
        /// </summary>
        /// <value><c>true</c> if initialized; otherwise, <c>false</c>.</value>
        public bool Inited
        {
            get { return inited; }
        }

        /// <summary>
        /// Gets the <see cref="Moonlite.Data.Configuration.ConfigurationElement"/> with the specified name.
        /// </summary>
        /// <value>The <see cref="Moonlite.Data.Configuration.ConfigurationElement"/> with the specified name.</value>
        public ConfigurationElement this[string name]
        {
            get
            {
                foreach (ConfigurationElement element in elements)
                {
                    if (element.Name == name)
                    {
                        return element;
                    }
                }

                return null;
            }
        }

        /// <summary>
        /// Dumps the specified instance into the <see cref="System.IO.StreamWriter"/>.
        /// </summary>
        /// <param name="writer">The writer.</param>
        void IDumpable.Dump(StreamWriter writer)
        {
            writer.Write("[ConfigurationInstance]");
            writer.Write("[elements = {");
            foreach (IDumpable element in elements)
            {
                element.Dump(writer);
            }

            writer.Write("}]");
        }

        /// <summary>
        /// Called when a <see cref="ConfigurationElement"/> property has been changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnConfigurationElementPropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(sender, e);
            }
        }

        /// <summary>
        /// Gets the <see cref="ConfigurationElement"/> represented by the given name.
        /// </summary>
        /// <param name="name">The name.</param>
        /// <returns>The found <see cref="ConfigurationElement"/>.</returns>
        public ConfigurationElement GetElementByName(string name)
        {
            foreach (ConfigurationElement element in elements)
            {
                if (element.Name == name)
                {
                    return element;
                }
            }

            return null;
        }

        /// <summary>
        /// Reads the instance from the specified node.
        /// </summary>
        /// <param name="node">The node.</param>
        public void Read(XmlNode node)
        {
            if (node.Name.ToLower() == "instance")
            {
                foreach (XmlNode childNode in node.ChildNodes)
                {
                    if (childNode.Name.ToLower() == "element")
                    {
                        Type elementType = null;
                        string fullname = "";
                        foreach (XmlAttribute attribute in childNode.Attributes)
                        {
                            if (attribute.Name.ToLower() == "type")
                            {
                                elementType = TypeGetHelpers.GetTypeFromFullName(attribute.Value);
                                fullname = attribute.Value;
                            }
                        }

                        if (elementType != null)
                        {
                            ConfigurationElement element = ConfigurationElement.CreateElement(elementType);
                            element.PropertyChanged += OnConfigurationElementPropertyChanged;
                            element.Read(childNode);
                            elements.Add(element);
                        }
                        else
                        {
                            ExceptionHelper.ThrowUnableToGetTypeFromFullNameException(fullname);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Writes the instance to the specified document as the root node.
        /// </summary>
        /// <param name="document">The document.</param>
        public void Write(XmlDocument document)
        {
            XmlNode instance = document.CreateNode(XmlNodeType.Element, "instance", null);
            XmlAttribute guidAttribute = document.CreateAttribute("guid");
            XmlAttribute typeAttribute = document.CreateAttribute("type");
            guidAttribute.Value = Guid.NewGuid().ToString();
            typeAttribute.Value = GetType().FullName;
            instance.Attributes.Append(guidAttribute);
            instance.Attributes.Append(typeAttribute);
            foreach (ConfigurationElement element in elements)
            {
                element.Write(document, instance);
            }

            document.AppendChild(instance);
        }

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected internal virtual void Init()
        {
            inited = true;
        }

        /// <summary>
        /// Creates a new instance based on the specified type.
        /// </summary>
        /// <param name="type">The type.</param>
        /// <returns>The created instance.</returns>
        internal static ConfigurationInstance CreateInstance(Type type)
        {
            ConfigurationInstance instance = (ConfigurationInstance)Activator.CreateInstance(type, true);
            instance.Init();
            return instance;
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="ConfigurationInstance"/> class.
        /// </summary>
        protected ConfigurationInstance()
        {
            Type type = GetType();
            Assembly assembly = type.Assembly;
            AttachmentRegister.RegisterPair(new AssemblyConfigurationAttachment(assembly, type));
        }
    }
}
